#include "stdafx.h"
#include "String.h"

#include <vector>

#ifdef WIN32
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif //_DEBUG
#endif //WIND32

#ifdef _UNICODE
#include "StringW.inl"
#else
#include "StringA.inl"
#endif

namespace serika
{
	namespace ut
	{
		// static
		void StringHelper::ClearMbstate(std::mbstate_t& mbs)
		{
			// Initialize/clear mbstate_t type.
			// XXX: This is just a hack that works. The shape of mbstate_t varies
			// from single unsigned to char[128]. Without some sort of initialization
			// the codecvt::in/out methods randomly fail because the initial state is
			// random/invalid.
			::memset(&mbs, 0, sizeof (std::mbstate_t));
		}

		// static
		void StringHelper::ToWStringInternal(std::wstring& outstr, const char* src, size_t size, const std::locale& loc)
		{
			if (0 == size)
			{
				outstr = L"";
				return;
			}

			typedef std::codecvt<wchar_t, char, std::mbstate_t> CodeCvt;
			const CodeCvt & cdcvt = std::use_facet<CodeCvt>(loc);
			std::mbstate_t state;
			StringHelper::ClearMbstate(state);

			char const * from_first = src;
			size_t const from_size = size;
			char const * const from_last = from_first + from_size;
			char const * from_next = from_first;

			std::vector<wchar_t> dest(from_size*2);

			wchar_t * to_first = &dest.front();
			size_t to_size = dest.size();
			wchar_t * to_last = to_first + to_size;
			wchar_t * to_next = to_first;

			CodeCvt::result result;
			size_t converted = 0;
			while (true)
			{
				result = cdcvt.in(
					state, from_first, from_last,
					from_next, to_first, to_last,
					to_next);
				// XXX: Even if only half of the input has been converted the
				// in() method returns CodeCvt::ok. I think it should return
				// CodeCvt::partial.
				if ((result == CodeCvt::partial || result == CodeCvt::ok)
					&& from_next != from_last)
				{
					to_size = dest.size() * 2;
					dest.resize(to_size);
					converted = to_next - to_first;
					to_first = &dest.front();
					to_last = to_first + to_size;
					to_next = to_first + converted;
					continue;
				}
				else if (result == CodeCvt::ok && from_next == from_last)
					break;
				else if (result == CodeCvt::error
					&& to_next != to_last && from_next != from_last)
				{
					StringHelper::ClearMbstate(state);
					++from_next;
					from_first = from_next;
					*to_next = L'?';
					++to_next;
					to_first = to_next;
				}
				else
					break;
			}
			converted = to_next - &dest[0];

			outstr.assign(dest.begin(), dest.begin() + converted);
		}

		// static 
		std::wstring StringHelper::ToWString(const std::string& src, const std::locale& loc)
		{
			std::wstring ret;
			StringHelper::ToWStringInternal(ret, src.c_str(), src.size(), loc);
			return ret;
		}

		// static
		std::wstring StringHelper::ToWString(char const* src, const std::locale& loc)
		{
			std::wstring ret;
			StringHelper::ToWStringInternal(ret, src, std::strlen (src), loc);
			return ret;
		}

		// static
		void StringHelper::ToStringInternal(std::string & outstr, const wchar_t * src, size_t size, const std::locale& loc)
		{
			if (0 == size)
			{
				outstr = "";
				return;
			}

			typedef std::codecvt<wchar_t, char, std::mbstate_t> CodeCvt;
			const CodeCvt & cdcvt = std::use_facet<CodeCvt>(loc);
			std::mbstate_t state;
			StringHelper::ClearMbstate(state);

			wchar_t const * from_first = src;
			size_t const from_size = size;
			wchar_t const * const from_last = from_first + from_size;
			wchar_t const * from_next = from_first;

			std::vector<char> dest(from_size);

			char * to_first = &dest.front();
			size_t to_size = dest.size();
			char * to_last = to_first + to_size;
			char * to_next = to_first;

			CodeCvt::result result;
			size_t converted = 0;
			while (from_next != from_last)
			{
				result = cdcvt.out(
					state, from_first, from_last,
					from_next, to_first, to_last,
					to_next);
				// XXX: Even if only half of the input has been converted the
				// in() method returns CodeCvt::ok with VC8. I think it should
				// return CodeCvt::partial.
				if ((result == CodeCvt::partial || result == CodeCvt::ok)
					&& from_next != from_last)
				{
					to_size = dest.size() * 2;
					dest.resize(to_size);
					converted = to_next - to_first;
					to_first = &dest.front();
					to_last = to_first + to_size;
					to_next = to_first + converted;
				}
				else if (result == CodeCvt::ok && from_next == from_last)
					break;
				else if (result == CodeCvt::error
					&& to_next != to_last && from_next != from_last)
				{
					StringHelper::ClearMbstate(state);
					++from_next;
					from_first = from_next;
					*to_next = '?';
					++to_next;
					to_first = to_next;
				}
				else
					break;
			}
			converted = to_next - &dest[0];

			outstr.assign(dest.begin(), dest.begin() + converted);
		}

		// static
		std::string StringHelper::ToString(const std::wstring& src, const std::locale& loc)
		{
			std::string ret;
			StringHelper::ToStringInternal(ret, src.c_str(), src.size(), loc);
			return ret;
		}

		// static
		std::string StringHelper::ToString(wchar_t const* src, const std::locale& loc)
		{
			std::string ret;
			StringHelper::ToStringInternal(ret, src, std::wcslen(src), loc);
			return ret;
		}
	}
}
